#include "Player.h"
#include "ControlState.h"
#include "Define.h"
#include "Controls.h"
#include "Camera.h"
#include "Level.h"
#include "Resources.h"
#include "GameTime.h"
#include "PlayerState.h"
#include "PlayerStateIdle.h"
#include "PlayerStateJump.h"
#include "PlayerStateFalling.h"
#include "PlayerStateAttackAir.h"
#include "PlayerStateLand.h"
#include "PlayerStateMoving.h"
#include "PlayerStateAttacking.h"
#include "PlayerStateDash.h"
#include "PlayerStateAirDash.h"
#include "PlayerStateDashRetract.h"
#include "PlayerStateWallSlide.h"
#include "PlayerStateWallJump.h"
#include "PlayerStateClimb.h"
#include "PlayerStateTakeDamage.h"
#include "EntityManager.h"
#include "CheckLadderBelow.h"
#include "CollisionTypes.h"
#include "HUDManager.h"
#include "EnergyBar.h"
#include <exception>
#include <iostream>

Player::Player(std::shared_ptr<ControlState> controlState, const std::string& id, EntityManager* entityManager, HUDManager* hudManager)
{
	_normalDamage = 10;
	_component |= COMPONENT_DRAWABLE;
	_component |= COMPONENT_COLLIDEABLE;
	_ct.push_back(CT_PLAYER);
	_mf.push_back(CT_ENEMY);
	_mf.push_back(CT_LADDER);
	_entityManager = entityManager;
	_hudManager = hudManager;
	_hudManager->addObject(new EnergyBar("BAR_ZERO", sf::Vector2f(10.0f, 10.0f)));

	_type = "Player";
	_jumpLength = -60.0f;
	_jumpPower = 250.0f;
	_wallJumpLength = -40.0f;
	_dashLength = 700.0f;
	_airDashLength = 500.0f;
	_wallSlideSpeed = 30.0f;
	_speed = 100.0f;
	_climbSpeed = 50.0f;

	_allowFlipChange = true;
	_ignoreCollision = false;
	_canDash = true;
	
	_flashTimer.restart();
	_flashing = false;
	_alpha_i = ALPHA_I_NEGATIVE;

	_onGround = false;
	_dimension = sf::Vector2f(20.0f, 40.0f);
	_controlState = controlState;
	
	_position = sf::Vector2f(50.0f, 50.0f);
	_main.setPosition(_position);
	_killMe = false;
	
	_health = 100;
	_isJumping = false;
	_isWallJumping = false;

	_flip = 1.0f;

	Resources::getSingletonPtr()->loadSprite(_sprite, id);	// load spritesheet
	Resources::getSingletonPtr()->loadAnimations(_animation, "ZERO_ANIM");	// load animations
	Resources::getSingletonPtr()->loadAudio(_jumpSFX, "S_JUMP");	// load sound effects
	Resources::getSingletonPtr()->loadAudio(_dashSFX, "S_DASH");
	Resources::getSingletonPtr()->loadAudio(_landSFX, "S_LAND");
	Resources::getSingletonPtr()->loadAudio(_swing01SFX, "S_SABERSWING01");
	Resources::getSingletonPtr()->loadAudio(_swing02SFX, "S_SABERSWING02");
	Resources::getSingletonPtr()->loadAudio(_swing03SFX, "S_SABERSWING03");
	Resources::getSingletonPtr()->loadAudio(_dmgReceiveSFX, "S_PLAYER_DMG_RCV");
	_dashMultiplier = 1.0f;
	_msX = MSX_NONE;
	_msY = MSY_NONE;
	_mass = 1.0f;
	removeFlag(GHOST);
	removeFlag(LADDER_BELOW);
	removeFlag(TAKE_DAMAGE);
	_playerState = new PlayerStateFalling(this);
	_playerState->activate();

	_pStateFunc.resize(PSRV_SIZE);
	_pStateFunc[PSRV_IDLE]				= [this] { _playerState = new PlayerStateIdle(this); };
	_pStateFunc[PSRV_ATTACK]			= [this] { _playerState = new PlayerStateAttacking(this, &_swing01SFX, &_swing02SFX, &_swing03SFX, _entityManager); };
	_pStateFunc[PSRV_ATTACK_AIR]		= [this] { _playerState = new PlayerStateAttackAir(this, &_swing01SFX, _entityManager); };
	_pStateFunc[PSRV_CLIMB]				= [this] { _playerState = new PlayerStateClimb(this, CLIMB_STATE_GRAB); };
	_pStateFunc[PSRV_CLIMB_DOWN]		= [this] { _playerState = new PlayerStateClimb(this, CLIMB_STATE_CLIMB_DOWN); };
	_pStateFunc[PSRV_DASH]				= [this] { _playerState = new PlayerStateDash(this, &_dashSFX); };
	_pStateFunc[PSRV_DASH_AIR]			= [this] { _canDash = false; _playerState = new PlayerStateAirDash(this, &_dashSFX); };
	_pStateFunc[PSRV_DASH_RETRACT]		= [this] { _playerState = new PlayerStateDashRetract(this); };
	_pStateFunc[PSRV_FALLING]			= [this] { _playerState = new PlayerStateFalling(this); };
	_pStateFunc[PSRV_JUMPING]			= [this] { _playerState = new PlayerStateJump(this, &_jumpSFX); };
	_pStateFunc[PSRV_LAND]				= [this] { _playerState = new PlayerStateLand(this, &_landSFX); };
	_pStateFunc[PSRV_MOVING_GROUND]		= [this] { _playerState = new PlayerStateMoving(this); };
	_pStateFunc[PSRV_TAKE_DAMAGE]		= [this] { _playerState = new PlayerStateTakeDamage(this); _dmgReceiveSFX.play(); };
	_pStateFunc[PSRV_WALL_JUMP]			= [this] { _playerState = new PlayerStateWallJump(this, &_jumpSFX); };
	_pStateFunc[PSRV_WALL_SLIDING]		= [this] { _playerState = new PlayerStateWallSlide(this); };
}

Player::~Player(void){}


void Player::move()
{
	_velocity.x = 0.0f;

	if (_controlState->isKeyDown(CControls::Key(CTRL_LEFT, 0), 0))
	{
		if (_allowFlipChange)
			_flip = -1.0f;
		_msX = MSX_LEFT;
	}
	else if(_controlState->isKeyDown(CControls::Key(CTRL_RIGHT, 0), 0))
	{
		if (_allowFlipChange)
			_flip = 1.0f;
		_msX = MSX_RIGHT;
	}
	else
		_msX = MSX_NONE;

	if (_controlState->isKeyDown(CControls::Key(CTRL_UP, 0), 0))
		_msY = MSY_UP;
	else if(_controlState->isKeyDown(CControls::Key(CTRL_DOWN, 0), 0))
		_msY = MSY_DOWN;
	else
		_msY = MSY_NONE;

	if (_playerState != nullptr)
	{
		int msg = _playerState->update();
		if (msg != PSRV_NOTHING)
			changeState(msg);
	}
}

void Player::update()
{
	if ( (flags() & LADDER_COLLISION) == LADDER_COLLISION )
		removeFlag(LADDER_COLLISION);

	_animation.update();
}

void Player::initialize(){}

void Player::draw(RenderManager* renderManager)
{
	_sprite.setScale(_flip, 1.0f);
	_sprite.setPosition(_position.x, _position.y);
	sf::IntRect rect = _sprite.getTextureRect();
	rect.left = _animation.getCurrentFrame() * rect.width;
	rect.top = _animation.getCurrentRow() * rect.height;
	_sprite.setTextureRect(rect);
	Camera::getSingleton()->goTo(_position);

	sf::RectangleShape rectc(sf::Vector2f(collisionBox().width, collisionBox().height));
	rectc.setPosition(collisionBox().left, collisionBox().top);
	//renderManager->draw(rectc);
	sf::RectangleShape rectcg(sf::Vector2f(collisionBox(GROUND_SENSOR).width, collisionBox(GROUND_SENSOR).height));
	rectcg.setPosition(collisionBox(GROUND_SENSOR).left, collisionBox(GROUND_SENSOR).top);
	rectcg.setFillColor(sf::Color::Green);
	//renderManager->draw(rectcg);
	rectcg = sf::RectangleShape(sf::Vector2f(collisionBox(FLOOR_SENSOR_AFTER_MOVEMENT).width, collisionBox(FLOOR_SENSOR_AFTER_MOVEMENT_X).height));
	rectcg.setPosition(collisionBox(FLOOR_SENSOR_AFTER_MOVEMENT_X).left, collisionBox(FLOOR_SENSOR_AFTER_MOVEMENT_X).top);
	rectcg.setFillColor(sf::Color::Magenta);
	//renderManager->draw(rectcg);
	/*rectcg = sf::RectangleShape(sf::Vector2f(collisionBox(RIGHT_SENSOR).width, collisionBox(RIGHT_SENSOR).height));
	rectcg.setPosition(collisionBox(RIGHT_SENSOR).left, collisionBox(RIGHT_SENSOR).top);
	rectcg.setFillColor(sf::Color::Green);
	renderManager->draw(rectcg);*/

	if (_flashing)
	{
		sf::Color c = _sprite.getColor();
		if (_flashTimer.getElapsedTime().asSeconds() >= 4.0f)
		{
			_canTakeDamage = true;
			_flashing = false;
			c.a = 255;
		}
		else
			interpolateAlpha(c);
		_sprite.setColor(c);
	}

	renderManager->draw(_sprite);
}

void Player::clean(){}

sf::FloatRect Player::collisionBox(int type)
{
	switch (type)
		{
		case VELOCITY_NORMAL:
			return sf::FloatRect(_position.x - (_dimension.x * 0.5f), _position.y - (_dimension.y * 0.5f), _dimension.x, _dimension.y);
		case VELOCITY_AFTER_MOVEMENT:
			return sf::FloatRect((_position.x - (_dimension.x * 0.5f)) + _velocity.x, (_position.y - (_dimension.y * 0.5f)) + _velocity.y, _dimension.x, _dimension.y);
		case VELOCITY_AFTER_MOVEMENT_X:
			return sf::FloatRect((_position.x - (_dimension.x * 0.5f)) + _velocity.x, _position.y - (_dimension.y * 0.5f), _dimension.x, _dimension.y);
		case VELOCITY_AFTER_MOVEMENT_Y:
			return sf::FloatRect(_position.x - (_dimension.x * 0.5f), (_position.y - (_dimension.y * 0.5f)) + _velocity.y, _dimension.x, _dimension.y);
		case GROUND_SENSOR:
			return sf::FloatRect(_position.x - (_dimension.x * 0.5f), _position.y + (_dimension.y * 0.5f) - 2.0f, _dimension.x, 2.0f);
		case GROUND_SENSOR_AFTER_MOVEMENT:
			return sf::FloatRect((_position.x - (_dimension.x * 0.5f)) + _velocity.x, (_position.y + (_dimension.y * 0.5f) - 2.0f) + _velocity.y, _dimension.x, 2.0f);
		case GROUND_SENSOR_AFTER_MOVEMENT_X:
			return sf::FloatRect((_position.x - (_dimension.x * 0.5f)), (_position.y + (_dimension.y * 0.5f) - 2.0f), _dimension.x, 2.0f);
		case GROUND_SENSOR_AFTER_MOVEMENT_Y:
			return sf::FloatRect((_position.x - (_dimension.x * 0.5f)) + _velocity.x, (_position.y + (_dimension.y * 0.5f) - 2.0f) + _velocity.y, _dimension.x, 2.0f);
		case LEFT_SENSOR:
			return sf::FloatRect(_position.x - (_dimension.x * 0.5f) - 2.0f, _position.y - (_dimension.y * 0.5f), 2.0f, _dimension.y);
		case RIGHT_SENSOR:
			return sf::FloatRect(_position.x + (_dimension.x * 0.5f), _position.y - (_dimension.y * 0.5f), 2.0f, _dimension.y);
		case FLOOR_SENSOR:
			return sf::FloatRect(_position.x - (_dimension.x * 0.5f), _position.y + (_dimension.y * 0.5f), _dimension.x, 4.0f);
		case FLOOR_SENSOR_AFTER_MOVEMENT:
			return sf::FloatRect((_position.x - (_dimension.x * 0.5f)) + _velocity.x, (_position.y + (_dimension.y * 0.5f)) + _velocity.y, _dimension.x, 4.0f);
		case FLOOR_SENSOR_AFTER_MOVEMENT_X:
			return sf::FloatRect((_position.x - (_dimension.x * 0.5f)) + _velocity.x, _position.y + (_dimension.y * 0.5f), _dimension.x, 4.0f);
		case FLOOR_SENSOR_AFTER_MOVEMENT_Y:
			return sf::FloatRect(_position.x - (_dimension.x * 0.5f), (_position.y + (_dimension.y * 0.5f)) + _velocity.y, _dimension.x, 4.0f);
		}
	return sf::FloatRect(0.0f, 0.0f, 0.0f, 0.0f);
}

void Player::dash()
{
	_dashMultiplier = 2.0f;
}


void Player::applyVelocity(int value, float vi)
{
	switch (value)
	{
	case VA_MOVEMENT_STATE:
		{
			if (_msX == MSX_LEFT)
			{
				_velocity.x = -((_speed + vi) * _dashMultiplier) * GameTime::getSingleton()->deltaTime();
			}
			else if (_msX == MSX_RIGHT)
			{
				_velocity.x = ((_speed + vi) * _dashMultiplier) * GameTime::getSingleton()->deltaTime();
			}
			break;
		}
	case VA_FLIP_BASED:
		{
			if (_flip == 1.0f)
				_velocity.x = ((_speed + vi) * _dashMultiplier) * GameTime::getSingleton()->deltaTime();
			else if (_flip == -1.0f)
				_velocity.x = -((_speed + vi) * _dashMultiplier) * GameTime::getSingleton()->deltaTime();
			break;
		}
	case VA_FLIP_BASED_REVERSE:
		{
			if (_flip == 1.0f)
				_velocity.x = -((_speed + vi) * _dashMultiplier) * GameTime::getSingleton()->deltaTime();
			else if (_flip == -1.0f)
				_velocity.x = ((_speed + vi) * _dashMultiplier) * GameTime::getSingleton()->deltaTime();
			break;
		}
	}
}

void Player::changeState(int state)
{
	if (_playerState != nullptr)
	{
		_playerState->deactivate();
		delete _playerState;
		_playerState = nullptr;
	}
	
	_pStateFunc[state]();
	
	if (_playerState != nullptr)
	{
		_playerState->activate();
		#ifdef _DEBUG
			std::cout << "Changed player state to " << _playerState->ID() << "\n";
		#endif
	}
	else
	{
		throw "Player::changeState ERROR: state could not be found so _playerState is set to nullptr.";
	}
}

void Player::resetDash(bool value)
{
	_dashMultiplier = 1.0f;
	if (value)
		_canDash = true;
}

void Player::sendLadderCheckBelow()
{
	_entityManager->addEntity(new CheckLadderBelow(this));
}

void Player::takeDamage(int damage)
{
	_canTakeDamage = false;
	setFlag(TAKE_DAMAGE);
	_health -= damage;
}

void Player::startFlashing()
{
	_flashing = true;
	_flashTimer.restart();
	_alpha_i = ALPHA_I_NEGATIVE;
}

void Player::interpolateAlpha(sf::Color& color)
{
	float valpha = static_cast<float>(color.a);
	if (_alpha_i == ALPHA_I_NEGATIVE)
	{
		valpha = std::max(valpha - (4000.0f * GameTime::getSingleton()->deltaTime()), 0.0f);
		if (valpha == 0.0f)
			_alpha_i = ALPHA_I_POSITIVE;
	}
	else if (_alpha_i == ALPHA_I_POSITIVE)
	{
		valpha = std::min(valpha + (4000.0f * GameTime::getSingleton()->deltaTime()), 255.0f);
		if (valpha == 255.0f)
			_alpha_i = ALPHA_I_NEGATIVE;
	}
	color.a = static_cast<sf::Uint8>(valpha);
}